//////////////////////////////////////////////////////////////////////////////////
//	 Copyright 2011 by Yosef Grabivker, ryuho@homenetworksoft.com
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
//////////////////////////////////////////////////////////////////////////////////

//# iptables -A INPUT -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
//# iptables -A FORWARD -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
//# iptables -D FORWARD -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
//# iptables -A FORWARD -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
//# iptables -D FORWARD -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
//# iptables -A FORWARD -m mac --mac-source 00:1B:77:01:DE:8E -j DROP
#include <wx/wx.h>
#include <algorithm>

#ifdef LINUX
#include <wx/imaglist.h>
#endif

#include "userstab.h"
#include "searchpanel.h"
#include "logicmanager.h"
#include "persistenlist.h"
#include "utils.h"
#include "rcmdapp.h"
#include "mainwnd.h"


enum cListColumns  { cListColumnIP=0, cListColumnMAC, cListColumnType, cListColumnStatus, cListColumnAccess, cListColumnTime, cListColumnScheduler };

enum cIDS
{
	ID_LIST=8100
};

static SEARCHFIELD sf[]=
{
	{ cSearchTypeString,	cListColumnIP,		_("IP")			},
	{ cSearchTypeString,	cListColumnMAC,		_("MAC")		},
	{ cSearchTypeNumeric,	cListColumnType,	_("Type")		},
	{ cSearchTypeString,	cListColumnStatus,	_("Status")		},
	{ cSearchTypeString,	cListColumnAccess,	_("Access")		},
	{ cSearchTypeNumeric,	cListColumnTime,	_("Last seen")	},
	{ cSearchTypeNumeric,	cListColumnScheduler,_("Scheduler")	},
};

//TODO:to socket
bool IpCompare(const wxString& a, const wxString& b);

BEGIN_EVENT_TABLE(CUsersTab, wxPanel)
	EVT_LIST_COL_CLICK(ID_LIST, CUsersTab::OnColumn)
	EVT_MOUSE_EVENTS(CUsersTab::OnMouseEvent)
END_EVENT_TABLE()

CUsersTab::CUsersTab(class wxWindow* pParent):wxPanel(pParent), m_bSortAsc(true), CSearchImpl<CUsersTab>(*this),
	m_pController(NULL)
{
	wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);

	m_pSearch = new CSearchPanel(this, this);
	sizer->Add(m_pSearch, 0, wxALL|wxEXPAND);

	m_nSortCol= cListColumnIP;

	m_pList = new CLogCtrl<CUsersTab>(this, ID_LIST);

	SetSearchListPtr(m_pList);

	sizer->Add(m_pList, 1, wxALL|wxEXPAND);

	for (size_t i=0;i<sizeof(sf)/sizeof(sf[0]);i++)
	{
		m_pList->InsertColumn( sf[i].iFieldID, sf[i].strLabel );
		m_pList->SetColumnWidth( sf[i].iFieldID, i>2?100:140  );
	}

	wxBoxSizer* sizerBottom = new wxBoxSizer(wxHORIZONTAL);

	m_pWhiteList = new CPersistenList(this, cMACBlockRuleIdWL);
	sizerBottom->Add(m_pWhiteList, 1, wxRIGHT|wxEXPAND|wxALIGN_LEFT, 20);

	//sizerBottom->AddStretchSpacer();

	m_pBlackList = new CPersistenList(this, cMACBlockRuleIdBL);
	sizerBottom->Add(m_pBlackList, 1, wxLEFT|wxEXPAND|wxALIGN_RIGHT, 20);

	m_pController = new CListPairController(m_pWhiteList, m_pBlackList);

	m_pWhiteList->SetController(m_pController);
	m_pBlackList->SetController(m_pController);

	sizer->Add(sizerBottom, 1, wxALL|wxEXPAND, 10);

	SetSizer(sizer);

	m_pImageList = new wxImageList(16, 16);

	wxImage icnTmp;
	if ( icnTmp.LoadFile(wxGetApp().GetImageDir()+wxT("blacklist.png"),   wxBITMAP_TYPE_PNG) )
	{
		m_pImageList->Add( icnTmp );
	}

	if ( icnTmp.LoadFile(wxGetApp().GetImageDir()+wxT("whitelist.png"),   wxBITMAP_TYPE_PNG) )
	{
		m_pImageList->Add( icnTmp );
	}
	
	if ( icnTmp.LoadFile(wxGetApp().GetImageDir()+wxT("spy_access.png"),   wxBITMAP_TYPE_PNG) )
	{
		m_pImageList->Add( icnTmp );
	}


	m_pList->SetImageList(m_pImageList, wxIMAGE_LIST_SMALL);
	m_pList->SetImageList(m_pImageList, wxIMAGE_LIST_STATE);
}

CUsersTab::~CUsersTab(void)
{
}

//////////////////////////////////////////////////////////////////////////
//Data
wxString CUsersTab::FieldToString( int column, const ROUTERUSER& entry)
{
	wxString str;

	try
	{
		switch(column)
		{
			case cListColumnIP:
				str = entry.strIP;
			break;

			case cListColumnMAC:
				str = entry.strMAC;
			break;

			case cListColumnType:
				str = GenerateType(entry.type);
			break;

			case cListColumnStatus:
				str = GenerateStatus(entry);
			break;

			case cListColumnAccess:
				str = GenerateAccessStatus(entry);
			break;

			case cListColumnTime:
			{
				if ( entry.lLast )
				{
					wxDateTime dt(entry.lLast);
					str = dt.Format(_("%c"));
				}
			}
			break;

			case cListColumnScheduler:
			{
				str = GenerateSchedulerStatus(entry);
			}
			break;
		}
	}
	catch (...)
	{

	}

	return str;
}

wxString CUsersTab::GenerateType(cRouterUserType type)
{
	wxString str;

	switch( type )
	{
		case cEthernetUser:
			str = _("Ethernet");
		break;

		case cWirelessUser:
			str = _("Wireless");
		break;

		case cNotDetectedUser:
			str = _("Detecting");
		break;
	}

	return str;
}

wxString CUsersTab::GenerateStatus(const ROUTERUSER& entry)
{
	wxString str;

	switch( entry.status )
	{
		case cRouterUserNew:
			str = _("New");
		break;


		case cRouterUserOnline:
			str = _("Online");
		break;

		case cRouterUserOffline:
			str = _("Offline");
		break;
	}

	return str;
}

wxString CUsersTab::GenerateAccessStatus(const ROUTERUSER& entry)
{
	wxString str;

	switch ( entry.mbstatus )
	{
		case cRouterMacBlocWhiteList:
			str= _("White listed");
		break;

		case cRouterMacBlocBlackList:
			str= _("Black listed");
		break;
	}

	return str;
}

wxString CUsersTab::GenerateSchedulerStatus(const ROUTERUSER& entry)
{
	wxString str;

	switch( entry.schedstatus )
	{
		case cRouterSchedNone:
			if ( entry.mbstatus ==cRouterMacBlocWhiteList )
			{
				str = _("Disabled");
			}
			else
			{
				str = _("None");
			}
		break;

		case cRouterSchedOn:
			str = _("Access Enabled");
		break;

		case cRouterSchedBlocked:
			str = _("Access Blocked");
		break;
	}

	return str;
}


wxString CUsersTab::GetItemText(long item, long column, wxWindowID id)
{
	ROUTERUSER& entry = m_storage[item];
	return FieldToString(column, entry);
}


void CUsersTab::OnResponse(const COMMANDRESPONSE& resp )
{
	PROCTABLEWITHHEADER table;

	RemoveMACBlock();

	switch( resp.code )
	{
		case cCmdARPTable:
		{
			/*
			FILE* fp = fopen("c:\\arp.log","w+");
			fprintf(fp, resp.strData.c_str() );
			fclose(fp);
			*/

			//table = CLogicManager::ParseTableWithHeaders( resp.strData, 6, " \n\t" );

			OnArpTable( resp.strXML.c_str() );
		}
		break;

		case cCmdSetDetectedWirelessMAC:
		{
			UpdateConnectedClient( wxT(""), wxString::FromAscii( resp.strData.c_str()), true );
		}
		break;

		/*
		!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
		case cCmdWirelessStaTable:
		{
			if ( std::string::npos==resp.strData.find("assoclist") )
			{
				table = CLogicManager::ParseTableWithHeaders( resp.strData, 6, " \n\t"  );
			}
			else
			{
				table.headers.push_back("Mac");
				TEXTLIST lst = StrToList( wxString::FromAscii( resp.strData.c_str()));

				for(size_t i=0;i<lst.size();i++)
				{
					wxString strMAC = ExtractMACFromString(wxString::FromAscii( lst[i].c_str()));

					if ( !strMAC.empty() )
					{
						TABLEROW row;
						row.push_back( (const char*)strMAC.ToAscii() );
						table.rows.push_back( row );
					}
				}
			}

			OnWirelessTable( table );
		}
		break;
		*/

		case cCmdMACBlockExecuted:
		{
			if ( resp.lData == CODE_SCRIPT_EXEC_FAIL )
			{
				m_pWhiteList->RemoveAll();
				m_pBlackList->RemoveAll();

				m_pBlackList->Enable( false );
				m_pWhiteList->Enable( false );

			}
		}
		break;


		default:
		break;
	}

	AddMacBlockData();
	ProcessData();
}

void::CUsersTab::AddMacBlockData()
{
	MACBLOCK lstWhite;
	MACBLOCK lstBlack;

	try
	{
		lstWhite  = wxGetApp().GetMainWnd()->GetPropStorage().GetMACList( cMACBlockRuleIdWL  );
		lstBlack  = wxGetApp().GetMainWnd()->GetPropStorage().GetMACList( cMACBlockRuleIdBL  );
	}catch(...) {}

	

	AddMacBlockData(lstWhite);
	AddMacBlockData(lstBlack);
}

void CUsersTab::AddMacBlockData(const MACBLOCK& block)
{
	for (size_t i=0;i<block.list.size();++i)
	{
		const MACENTRY& entry = block.list[i];
		bool bFound(false);

		for ( ROUTERUSERS::iterator iCur = m_entriesBeforeFilter.begin();
			iCur!=m_entriesBeforeFilter.end();++iCur )
		{
			ROUTERUSER& user = *iCur;

			if ( IsMACEqual(wxString::FromAscii(entry.strMAC.c_str()),
							iCur->strMAC) )
			{
				user.mbstatus = block.id==cMACBlockRuleIdWL?cRouterMacBlocWhiteList:cRouterMacBlocBlackList;
				FillBlockDetails(user, entry);
				bFound = true;
			}
		}

		if (!bFound)
		{
			ROUTERUSER user;
			user.bFromMacBlock = true;
			user.mbstatus = block.id==cMACBlockRuleIdWL?cRouterMacBlocWhiteList:cRouterMacBlocBlackList;
			user.strMAC = wxString::FromAscii(entry.strMAC.c_str());
			FillBlockDetails(user, entry);
			m_entriesBeforeFilter.push_back( user );
		}
	}
}

void CUsersTab::FillBlockDetails(ROUTERUSER& user, const MACENTRY& entry)
{
	user.schedstatus = cRouterSchedNone;

	if ( user.mbstatus == cRouterMacBlocWhiteList &&
		 entry.bEnableSched )
	{
		if ( entry.bEnableSched &&
			 entry.IsBlockedByScheduler() )
		{
			user.schedstatus = cRouterSchedBlocked;
		}
		else
		{
			user.schedstatus = cRouterSchedOn;
		}
	}
}

void CUsersTab::RemoveMACBlock()
{
	for ( ROUTERUSERS::iterator iCur = m_entriesBeforeFilter.begin();
		iCur!=m_entriesBeforeFilter.end();++iCur )
	{
		if ( iCur->bFromMacBlock )
		{
			m_entriesBeforeFilter.erase(iCur);
			RemoveMACBlock();
			break;
		}
		else
		{
			iCur->mbstatus = cRouterMacBlockNone;
			iCur->schedstatus = cRouterSchedNone;
		}
	}
}


void CUsersTab::OnArpTable( const char* szXML )
{
	time_t tNow = time(NULL);

	CMarkupSTL	doc;

	if ( doc.SetDoc(szXML) )
	{
		if ( !doc.FindElem(ARP_ROOT_EL) ) return;

		while ( doc.FindChildElem(GLOBAL_ENTRY_EL) )
		{
			wxString strIP = wxString::FromAscii( doc.GetChildAttrib(GLOBAL_IP_ATTR).c_str() );
			wxString strMAC = wxString::FromAscii( doc.GetChildAttrib(GLOBAL_MAC_ATTR).c_str() );

			UpdateConnectedClient(strIP, strMAC);
		}
	}

	for ( size_t j=0;j<m_entriesBeforeFilter.size();j++ )
	{
		ROUTERUSER& user = m_entriesBeforeFilter[j];

		//For new users only
		if ( user.type == cNotDetectedUser )
		{
			user.type = cEthernetUser;
		}
	}
}

void CUsersTab::UpdateConnectedClient(const wxString& strIP, const wxString& strMAC, bool bForceWireless/*=false*/)
{
	bool bFound(false);

	time_t tNow = time(NULL);

	for ( size_t j=0;j<m_entriesBeforeFilter.size();j++ )
	{
		ROUTERUSER& user = m_entriesBeforeFilter[j];

		cRouterUserStatus cPrevStatus = user.status;

		//mark offline users
		if ( tNow-user.lLast > 30  )
		{
			user.status = cRouterUserOffline;
		}

		if ( user.strMAC == strMAC )
		{
			user.lLast  = tNow;
			user.status = cRouterUserOnline;

			if ( !strIP.empty() )
			{
				user.strIP  = strIP;
			}

			if ( bForceWireless )
			{
				user.type = cWirelessUser;
			}

			bFound = true;
		}

		if ( user.status!=cPrevStatus )
		{
			LOGENTRY entry;

			if ( (cPrevStatus == cRouterUserOffline || cPrevStatus == cRouterUserNew) &&
				  user.status == cRouterUserOnline )
			{
				entry.strCustomEvent = GenerateType(user.type);
				entry.strCustomEvent+=wxT(" ");
				entry.strCustomEvent+= _("LAN client detected, IP:");
				entry.strCustomEvent+=user.strIP;
				entry.strCustomEvent+=wxT(", ");
				entry.strCustomEvent+=_("MAC");
				entry.strCustomEvent+=wxT(":");
				entry.strCustomEvent+=user.strMAC;
				wxGetApp().GetMainWnd()->GetLogic().AddToLog(entry);
			}


			if (  cPrevStatus ==  cRouterUserOnline &&
				  user.status == cRouterUserOffline )

			{
				LOGENTRY entry;
			
				entry.strCustomEvent = GenerateType(user.type);
				entry.strCustomEvent+=wxT(" ");
				entry.strCustomEvent+= _("LAN client disconnected, IP:");
				entry.strCustomEvent+=user.strIP;
				entry.strCustomEvent+=wxT(", ");
				entry.strCustomEvent+=_("MAC");
				entry.strCustomEvent+=wxT(":");
				entry.strCustomEvent+=user.strMAC;

				wxGetApp().GetMainWnd()->GetLogic().AddToLog(entry);
			}
		}
	}

	//New entry
	if ( !bFound )
	{
		ROUTERUSER user;

		user.lLast	= tNow;
		user.status = cRouterUserNew;
		user.type	= cNotDetectedUser; //must be this
		user.strMAC = strMAC;
		user.strIP  = strIP;

		m_entriesBeforeFilter.push_back(user);
	}	
}


/*
void CUsersTab::OnWirelessTable( const PROCTABLEWITHHEADER& table )
{
	m_tableWireless = table;
}
*/


void CUsersTab::ProcessData()
{
	try
	{
		ResetSearchPos();

		DoFilter();

		DoSort();

		((CLogCtrl<CUsersTab>*)m_pList)->DataChanged();
	}
	catch(...)
	{
	}
}


////////////////////////////////////////////////////////////////////////////
//Sort
bool CUsersTab::CSortHelper::operator() (const ROUTERUSER& a, const ROUTERUSER& b)
{
	bool bRes(false);

	switch (m_col)
	{
		case cListColumnIP:
			bRes = IpCompare(a.strIP, b.strIP);
		break;

		case cListColumnMAC:
			bRes = a.strMAC<b.strMAC;
		break;

		case cListColumnType:
			bRes = a.type<b.type;
		break;

		case cListColumnStatus:
			bRes = a.status<b.status;
		break;

		case cListColumnAccess:
			bRes = a.mbstatus<b.mbstatus;
		break;

		case cListColumnTime:
			bRes = a.lLast<b.lLast;
		break;

		case cListColumnScheduler:
			bRes = a.schedstatus<b.schedstatus;
		break;
	}

	return m_bSortAsc?bRes:!bRes;
}

void CUsersTab::DoSort()
{
	CSortHelper sorter(m_nSortCol, m_bSortAsc);
	std::sort( m_storage.begin(), m_storage.end(), sorter);
}


void CUsersTab::OnColumn(wxListEvent& ev)
{
	m_bSortAsc=!m_bSortAsc;

	m_nSortCol = ev.m_col;

	DoSort();
	
	((CLogCtrl<CUsersTab>*)m_pList)->DataChanged();
}


//////////////////////////////////////////////////////////////////////////
//Filter
void CUsersTab::DoFilter()
{
	wxString strVal;

	m_storage.erase( m_storage.begin(), m_storage.end() );

	for(size_t i=0;i<m_entriesBeforeFilter.size();i++)
	{
		bool bValid(true);

		for (size_t j=0;j<sizeof(sf)/sizeof(sf[0]);j++)
		{
			strVal = FieldToString( sf[j].iFieldID, m_entriesBeforeFilter[i] );

			if ( !m_pSearch->Match(sf[j], strVal) )
			{
				bValid = false;
				break;
			}
		}

		if ( bValid )
		{
			m_storage.push_back( m_entriesBeforeFilter[i] );
		}
	}
}

////////////////////////////////////////////////////////////////////////////
//ISearchProvider
void CUsersTab::GetSearchFields(SEARCHFIELD*& sfRes, size_t& sz)
{
	sfRes = sf;
	sz = sizeof(sf)/sizeof(sf[0]);
}

void CUsersTab::OnFilterChanged()
{
	ProcessData();
}

//////////////////////////////////////////////////////////////////////////////////////////////
//Drag and drop
void CUsersTab::OnMouseEvent(wxMouseEvent& event)
{
	/*
	if ( event.LeftIsDown() )
	{
		//wxTextDataObject my_data( _("This text will be dragged.") );
		wxDropSource dragSource( this );
		dragSource.SetData( ((CLogCtrl<CUsersTab>*)m_pList)->GetDataObject() );
		wxDragResult result = dragSource.DoDragDrop( true );

	}*/
}

////////////////////////////////////////////////////////////////////////////
int	 CUsersTab::GetItemColumnImage(long item, long column, wxWindowID id/*=wxID_ANY*/)
{
	int nRes(-1);

	if ( 0==column )
	{
		ROUTERUSER& entry = m_storage[item];

		switch (entry.mbstatus)
		{
			case cRouterMacBlocBlackList:
				nRes = 0;
			break;

			case cRouterMacBlocWhiteList:
				nRes = 1;
			break;

			case cRouterMacBlockNone:
				nRes = 2;
			break;

		}
	}

	return nRes;
}

wxString CUsersTab::GetLog()
{
	wxString strStatus, strVal;

	if ( wxGetApp().GetMainWnd()->GetPropStorage().GetEnableAccessLog() )
	{
		strStatus = _("Devices connected");
		strStatus+=wxT(": ");
		strStatus+=_("Total");
		strStatus+=wxT(": ");
		strStatus+= wxString::Format(wxT("%u. "), m_entriesBeforeFilter.size() );


		bool bFound(false);

		for(size_t i=0;i<m_entriesBeforeFilter.size();i++)
		{
			if ( m_storage[i].status!=cRouterUserOffline )
			{
				wxString strLine;

				for (size_t j=0;j<sizeof(sf)/sizeof(sf[0])-1;j++)
				{
					if (!strLine.empty())
					{
						strLine+=wxT(", ");
					}

					strLine+= sf[j].strLabel;
					strLine+= wxT(": ");
					strLine+= FieldToString( sf[j].iFieldID, m_entriesBeforeFilter[i] );
					
					bFound = true;
				}

				strStatus+=strLine;
				strStatus+=wxT(". ");
			}
		}

		if ( !bFound )
		{
			strStatus = _("No connections detected");
		}
	}

	return strStatus;
}
